import { Clock, Matrix4, sRGBEncoding, Vector3,PMREMGenerator, SpotLight, SpotLightHelper, Scene, WebGLRenderer, PerspectiveCamera, DirectionalLight, Color, HemisphereLight, } from '../libs/three146/three.module.js';
import * as CANNON from '../libs/cannon-es.js'; // https://pmndrs.github.io/cannon-es/docs/
import { CannonHelper } from '../libs/CannonHelper.js';
import { OrbitControls } from '../libs/three146/OrbitControls.js';
import { Table } from './Table.js';
import { Ball } from './Ball.js';
import { RGBELoader } from '../libs/three146/RGBELoader.js';
import { urls } from '../assets/store.js';
import { GLTFLoader } from '../libs/three146/GLTFLoader.js';
import { LoadingBar } from '../libs/LoadingBar.js';
import { WhiteBall } from './WhiteBall.js';
import { StrengthBar } from './StrengthBar.js';
import { GameState } from './gameState.js';
const { random, PI } = Math
class Game {
  constructor() {
    this.initThree();
    this.initWorld();
    this.initScene();
    this.gameState = new GameState(this) ;
/* 
    this.strengthBar = new StrengthBar();
    const strengthControl = this.strengthBar.strengthBar;
    if ('ontouchstart' in document) {
      strengthControl.addEventListener('touchstart', this.pointerdown);
      strengthControl.addEventListener('touchend', this.pointerup);
    } else {
      // 把鼠标事件放在力量条上以解决和轨道控制器的冲突
      strengthControl.addEventListener('pointerdown', this.pointerdown);
      strengthControl.addEventListener('pointerup', this.pointerup);
      window.addEventListener('keydown', this.keydown);
      window.addEventListener('keyup', this.keyup);
    }
 */
  }

  pointerdown = (e) => {
    this.strengthBar.visible = true;

  }
  pointerup = (e) => {
    this.strengthBar.visible = false;
    this.strikeCueball()
  }

  keydown = (e) => {
    if (e.key === ' ') {

      this.strengthBar.visible = true;
    }
  }

  keyup = (e) => {
    if (e.key === ' ' && !e.repeat) {

      this.strengthBar.visible = false;
      this.strikeCueball()
    }

  }

  strikeCueball() {
    if (this.cueball.isSleeping) { this.cueball.hit(this.gameState.ui.strengthBar.strength) };

  }
  initThree() {
    this.yAixs = new Vector3(0, 1, 0)
    const container = document.createElement('div');
    document.body.appendChild(container);
    this.loadingBar = new LoadingBar();
    this.camera = new PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 20);
    this.camera.position.set(-3, 1.5, 0);

    this.scene = new Scene();
    this.scene.background = new Color(0x0000000);

    const ambient = new HemisphereLight(0x0d0d0d, 0x020202, 0.1);
    this.scene.add(ambient);
   
    this.createLight(Table.LENGTH / 4)
    this.createLight(-Table.LENGTH / 4)

    this.renderer = new WebGLRenderer({ antialias: true });
    this.renderer.shadowMap.enabled = true;
    this.renderer.setPixelRatio(window.devicePixelRatio);
    this.renderer.setSize(window.innerWidth, window.innerHeight);
    this.renderer.outputEncoding = sRGBEncoding;
    this.renderer.physicallyCorrectLights = true; // 增强真实感

    container.appendChild(this.renderer.domElement);

    const controls = new OrbitControls(this.camera, this.renderer.domElement);
    this.controls = controls
    // controls.enableRotate =true  
    // controls.enablePan =false
    controls.maxDistance = 2
    controls.minDistance = .35 ;
    controls.maxPolarAngle = .7 ;// 限制极角  居然不是按照相机位置，而是反过来的 它的极角是y轴正向是 -PI/2    z这里就限制了观察底面   
    controls.minPolarAngle =-.7



    this.clock = new Clock()

    console.log(controls);

    this.renderer.setAnimationLoop(this.render.bind(this));

/*
    const mat1 = new Matrix4()
    const mat2 = new Matrix4()
    const mat3 = new Matrix4()
    window.addEventListener('keydown', (e) => {
      let dv = this.cueball.mesh.position.clone(), da = 0, db =0;
      //  一直绕着 y轴转，加上那个镜头跟随，相机越来越远了  要是直接操作control的球坐标多好

       mat2.makeTranslation(...dv) ;
       mat1.makeTranslation(-dv.x, -dv.y, -dv.z) ;
      console.log(e);
      if (e.key === 'ArrowLeft') {
        da = -.1
      } else if (e.key === 'ArrowRight') {
        da = .1
      }else if (e.key === 'ArrowUp') {
        db = -.1
      } else if (e.key === 'ArrowDown') {
        db = .1
      }
      
      // make系列api是直接改变矩阵， 不是在原本上乘
      mat3.makeRotationAxis(this.yAixs, da) ;
      if (da) {
        this.controls.rotateLeft(da);
        // this.camera.position.add(dv)
        // this.camera.rotateOnWorldAxis(this.yAixs, da); // 这个方法为什么改不了他的position
        // this.camera.matrix.multiply(mat1)
        // this.camera.matrix.multiply(mat2)
        // this.camera.matrix.multiply(mat3)
        
        // this.camera.applyMatrix4(mat1)// 只有这个有反应 奇了怪了
        // this.camera.applyMatrix4(mat3)// 只有这个有反应 奇了怪了
        // this.camera.applyMatrix4(mat2)// 只有这个有反应 奇了怪了
        // this.camera.updateMatrix() // 反应到position上
        // console.log(this.camera.position);
      }
      if(db){ 
        this.controls.rotateUp(db)
      }



    })*/
    window.addEventListener('resize', this.resize.bind(this));
  }
  // 聚光灯
  createLight(x) {
    const spotLight = new SpotLight(0xffffe5, 2.5, 10, .8, .5, 2);
    spotLight.position.set(x, 1.5, 0);
    spotLight.target.position.set(x, 0, 0);
    spotLight.target.updateMatrixWorld();

    spotLight.castShadow = true;
    spotLight.shadow.camera.fov = 70
    spotLight.shadow.camera.near = 1
    spotLight.shadow.camera.far = 2.5
    spotLight.shadow.mapSize.set(1024, 1024); // 2048
    this.scene.add(spotLight);

    const lightHepler = new SpotLightHelper(spotLight);
    // this.scene.add(lightHepler) ;

  }
  // 环境贴图
  setEnviroment() {
    const loader = new RGBELoader()
    const pmpremGenerator = new PMREMGenerator(this.renderer);
    pmpremGenerator.compileEquirectangularShader();
    loader.load(urls.living_room_hdr, texture => {
      const evnmap = pmpremGenerator.fromEquirectangular(texture).texture;
      pmpremGenerator.dispose();
      this.scene.environment = evnmap; //  只要是bpr 自身没有环境贴图的 就会使用这个
    })
  }

  loadGLTF() {
    const loadingBar = this.loadingBar
    const loader = new GLTFLoader();
    loader.load(urls.pool_table_glb, gltf => {
      this.table = gltf.scene;
      this.table.position.set(-Table.LENGTH / 2, 0, Table.WIDTH / 2);
      this.table.traverse(child => {
        if (child.name === 'Cue') {
          this.cue = child;
          child.visible = false;
        } else if (child.name === 'Felt') {
          this.edges = child;
        }

        if (child.type === 'Mesh') {
          child.material.metalness = 0;
          child.material.roughness = .3;
        }
        if (child?.parent?.name === 'Felt') {
          child.material.roughness = .8;
          child.receiveShadow = true;

        }

      })
      this.scene.add(this.table);
      loadingBar.visible = false;
      this.gameState.showPlayBtn()
    },
      xhr => {
        loadingBar.update('table', xhr.loaded , xhr.total)
      },
      err => {
        console.error(err)
      }
    )
  }
  initWorld() {
    const world = new CANNON.World();
    world.gravity.set(0, -9.82, 0);

    world.solver.iterations =10 ;
    world.solver.tolerance =0 ;

    world.allowSleep = true;
    world.fixedTimeStep = 1 / 60

    this.helper = new CannonHelper(this.scene, world);
    this.world = world;
    this.setCollisionBehaviour(world)
    // keypress 只有字符才能触发
    window.addEventListener('keydown', (e) => {

      // if(e.key !== ' ')return 
      // console.log(e);

      switch (e.key) {
        case ' ':
          // e.repeat || this.cueball.hit(Math.random()*.5 + .5)

          break;
        case 'Escape':
          this.balls.forEach((ball) => ball.reset())
        default:
          break;
      }
    })
  }
// 这里是定义两个材质直接相互作用性质的地方，也就说，单独给某个材质设置摩擦 反弹系数 无意义
  setCollisionBehaviour(world) {
        this.bottom = new CANNON.Body({shape: new CANNON.Plane(), material: new CANNON.Material({})})
    // Defines what happens when two materials meet. todo Refactor materials to materialA and materialB
    world.defaultContactMaterial.friction = .2; // 摩擦力
    world.defaultContactMaterial.restitution = .8; // 还原 应该就是说弹性损失 。2

    const ball_Floor = new CANNON.ContactMaterial(Ball.MATERIAL, Table.FLOOR_MATERIAL, { friction: .7, restitution: .1 })
    const ball_bottom = new CANNON.ContactMaterial(Ball.MATERIAL, Table.BOTTOm_MATERIAL, { friction: .8, restitution: .0 })
    world.addContactMaterial(ball_Floor);
    world.addContactMaterial(ball_bottom);
  }

  random(min, max) {
    const range = max - min;
    return random() * range + min;
  }
  // 物理引擎的场景
  initSceneOld() {
    const groundShape = new CANNON.Plane();
    const groundBody = new CANNON.Body({ mass: 0 }); // 地面没有质量才能静止
    groundBody.addShape(groundShape);
    groundBody.quaternion.setFromEuler(-PI / 2, 0, 0);
    this.world.addBody(groundBody);
    this.helper.addVisual(groundBody);
    this.bodies = []
    window.addEventListener('pointerdown', this.creatSphere.bind(this)) //  确实会看到小球先在原点出现 再出闪现到随机位置
  }

  initScene() {
    this.table = new Table(this)
    this.createBalls()
    this.setEnviroment()
    this.loadGLTF()
    console.log(this.scene);

  }
  createBalls() {
    this.balls = [new WhiteBall(this, -Table.LENGTH / 4, 0)];// 用于撞击的球 桌子中线 1/4处
    const rowInc = 1.74 * Ball.RADIUS; // 第一行放5个  下一行就放4个 
    let row = { x: Table.LENGTH / 4 + rowInc, count: 6, total: 6 };
    const ids = [4, 3, 14, 2, 15, 13, 7, 12, 5, 6, 8, 9, 10, 11, 1];

    for (let i = 0; i < 15; i++) {
      if (row.total === row.count) { // 开始新行
        row.total = 0;
        row.count--; // 下一行少放一个
        row.x -= rowInc;// 两行间隔 √3/2 * r
        row.z = (row.count - 1) * (Ball.RADIUS + .002);// 起点 就是全部球的长度的一半 减 一个半径 
      }
      this.balls.push(new Ball(this, row.x, row.z, ids[i]));
      row.z -= 2 * (Ball.RADIUS + .002);
      row.total++;
    }

    this.cueball = this.balls[0];
  }


  creatSphere() {

    const size = .4;
    const bodies = this.bodies;
    let i = 0;
    const sphereShape = new CANNON.Sphere(size);
    const sphereBody = new CANNON.Body({
      mass: 1,
      position: new CANNON.Vec3(random(-.1, 1), 4, random(-.1, .1))

    });
    sphereBody.addShape(sphereShape);
    this.world.addBody(sphereBody);
    this.helper.addVisual(sphereBody, 0xff0000);
    bodies.push(sphereBody);

    if (bodies.length > 80) {
      const bodyKill = bodies.shift();
      this.helper.removeVisual(bodyKill);
      this.world.removeBody(bodyKill);

    }
  }

  resize() {
    this.camera.aspect = window.innerWidth / window.innerHeight;
    this.camera.updateProjectionMatrix();
    this.renderer.setSize(window.innerWidth, window.innerHeight);
  }
  reset(){ 
    this.balls.forEach(ball=> ball.reset())
  }
  updateUi(e){ 
    switch(e.event){ 
      case 'balldrop': 
      this.gameState.coloredBallEnterHole(e.id);
      break;
      case 'whitedrop':
        this.gameState.whiteBallEnterHole();
    break ;
    default:
    break ;
    }
  }

  render() {
    window.debug  &&  this.helper.update()
    //  只要追踪 目标就行了 要是以前，我就把相机塞到这里球里，不过，这里不行，球在滚动。
    this.controls.target.copy(this.cueball.mesh.position)
    this.controls.update()
    const dt = this.clock.getDelta()
    this.gameState.update()
    this.world.step(.0167); //这是为了让物理引擎好处理 等时间段
    this.balls.forEach(ball => ball.update(dt));
    this.renderer.render(this.scene, this.camera);
  }
}

export { Game };